package xyz.xtt.equity.service.impl;

import static xyz.xtt.common.asset.AssertUtils.isFalse;
import static xyz.xtt.common.asset.AssertUtils.isNull;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.common.exception.ChainException;
import xyz.xtt.equity.api.dto.GetBenefitPurchaseRecordRequest;
import xyz.xtt.equity.api.dto.GetOnSaleBenefitPackageRequest;
import xyz.xtt.equity.api.dto.SaveBenefitPurchaseUnPayDto;
import xyz.xtt.equity.api.dto.TblBenefitPackageDto;
import xyz.xtt.equity.api.dto.TblBenefitPurchaseRecordDto;
import xyz.xtt.equity.api.enums.BenefitEnum;
import xyz.xtt.equity.api.enums.BenefitPurchasePayStatusEnum;
import xyz.xtt.equity.api.enums.UserBenefitSourceTypeEnum;
import xyz.xtt.equity.client.request.GetUnPayBenefitPurchaseRecordRequest;
import xyz.xtt.equity.client.request.HandlePayCancelRequest;
import xyz.xtt.equity.client.request.HandlePaySuccessRequest;
import xyz.xtt.equity.constants.ErrorMessage;
import xyz.xtt.equity.entity.BenefitMetaData;
import xyz.xtt.equity.entity.TblBenefit;
import xyz.xtt.equity.entity.TblBenefitPackage;
import xyz.xtt.equity.entity.TblBenefitPurchaseRecord;
import xyz.xtt.equity.entity.TblUserBenefit;
import xyz.xtt.equity.service.IBenefitService;
import xyz.xtt.equity.service.IInnerService;
import xyz.xtt.equity.service.INftVipService;
import xyz.xtt.equity.service.ITblBenefitPackageService;
import xyz.xtt.equity.service.ITblBenefitPurchaseRecordService;
import xyz.xtt.equity.service.ITblBenefitService;
import xyz.xtt.equity.service.ITblUserBenefitChangeRecordService;
import xyz.xtt.equity.service.ITblUserBenefitService;
import xyz.xtt.equity.utils.RedisLockUtil;
import xyz.xtt.equity.vo.BenefitItemVo;
import xyz.xtt.equity.vo.NftVipInfoVo;
import xyz.xtt.equity.vo.NftVipPlanVo;
import xyz.xtt.equity.vo.UserVipVO;
import xyz.xtt.pay.client.response.BusinessOrderResponse;

/**
 * @author dale
 * @date 2025/2/10
 **/
@Slf4j
@Service
public class NftVipServiceImpl implements INftVipService {
	private static final String BENEFIT_PURCHASE_BIZ_NFT_VIP_PURCHASE = "NFT_VIP_PURCHASE";
    private static final Integer USER_BENEFIT_BLOCKED = 1;

	@Autowired
	private ITblBenefitPurchaseRecordService tblBenefitPurchaseRecordService;
	@Autowired
	private ITblUserBenefitService tblUserBenefitService;
	@Autowired
	private ITblBenefitPackageService tblBenefitPackageService;
	@Autowired
	private ITblBenefitService tblBenefitService;
	@Autowired
	private ITblUserBenefitChangeRecordService tblUserBenefitChangeRecordService;
	@Autowired
	private IBenefitService benefitService;
	@Autowired
	private ITblBenefitService iTblBenefitService;
	@Autowired
	private RedisLockUtil redisLockUtil;
	@Autowired
	private IInnerService iInnerService;

	@Override
	public NftVipInfoVo queryVipInfo(Long uid) {
        NftVipInfoVo nftVipInfoVo = new NftVipInfoVo();
        List<BenefitItemVo> myBenefits = new ArrayList<>();
        List<NftVipPlanVo> vipPlans = new ArrayList<>();
        nftVipInfoVo.setMyBenefits(myBenefits);
        nftVipInfoVo.setVipPlans(vipPlans);

        // 查询当前生效中的vip信息
        UserVipVO vip = tblUserBenefitService.isVip(uid);
        nftVipInfoVo.setHasVip(vip.isHasVip());

        List<TblBenefitPackage> vipPackages = tblBenefitPackageService.getNftVipPackages();
        // 存储所有vip套餐的权益id，包括已下线的vip套餐。用来获取用户的权益信息
        List<Long> allBenefitIds = new ArrayList<>();
        for (TblBenefitPackage vipPackage : vipPackages) {
            List<TblBenefit> benefits = tblBenefitService.queryBenefitsByPackageId(vipPackage.getPackageId());
            if (Objects.equals(0, vipPackage.getIsDel()) && Objects.equals(1, vipPackage.getOnSale())) {
                List<BenefitItemVo> benefitItemVos = new ArrayList<>();
                for (TblBenefit benefit : benefits) {
                    allBenefitIds.add(benefit.getBenefitId());
                    if (Objects.equals(benefit.getIsDel(), 0)
                            && !Objects.equals(benefit.getBenefitCode(), BenefitEnum.NFT_VIP_TAG.getCode())) {
                        BenefitItemVo benefitItemVo = new BenefitItemVo();
                        benefitItemVo.setBenefitCode(benefit.getBenefitCode());
                        BenefitMetaData metaData = JSONUtil.toBean(benefit.getMetaJson(), BenefitMetaData.class);
                        benefitItemVo.setLimitCount(metaData.getLimitCount());
                        benefitItemVos.add(benefitItemVo);
                    }
                }
                NftVipPlanVo nftVipPlanVo = new NftVipPlanVo();
                nftVipPlanVo.setType(vipPackage.getPackageCode());
                nftVipPlanVo.setPrice(vipPackage.getSalePrice().toPlainString());
                nftVipPlanVo.setOriginalPrice(vipPackage.getOriginalPrice().toPlainString());
                nftVipPlanVo.setBenefits(benefitItemVos);
                vipPlans.add(nftVipPlanVo);
            } else {
                allBenefitIds.addAll(benefits.stream().map(TblBenefit::getBenefitId).collect(Collectors.toList()));
            }
        }

        if (vip.isHasVip()) {
            nftVipInfoVo.setExpireAt(
                    vip.getTblUserBenefit().getExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
                            + "");
            // 查询通过购买vip获得的权益信息（相同权益通过其他渠道获取的不在此处展示）
            fillUserVipBenefitData(uid, allBenefitIds, myBenefits);
        }

        return nftVipInfoVo;
    }

    @Override
    public TblBenefitPackageDto getOnSaleBenefitPackage(@Valid GetOnSaleBenefitPackageRequest request) {
        LambdaQueryChainWrapper<TblBenefitPackage> query = tblBenefitPackageService.lambdaQuery();
        TblBenefitPackage benefitPackage = query.eq(TblBenefitPackage::getPackageCode, request.getPackageCode())
                .eq(TblBenefitPackage::getOnSale, 1)
                .eq(TblBenefitPackage::getIsDel, 0).one();
        return convertPackageToDto(benefitPackage);
    }

    @Override
    public TblBenefitPurchaseRecordDto getUnPayBenefitPurchaseRecord(Long uid,
            GetUnPayBenefitPurchaseRecordRequest request) {
        LambdaQueryChainWrapper<TblBenefitPurchaseRecord> query = tblBenefitPurchaseRecordService.lambdaQuery();
        TblBenefitPurchaseRecord purchaseRecord = query.eq(TblBenefitPurchaseRecord::getAppUserId, uid)
                .eq(TblBenefitPurchaseRecord::getPackageId, request.getPackageId())
                .eq(TblBenefitPurchaseRecord::getPayStatus, BenefitPurchasePayStatusEnum.NOT_PAYED.code())
                .eq(TblBenefitPurchaseRecord::getIsDel, 0)
                .orderByDesc(TblBenefitPurchaseRecord::getCreateTime)
                .last("limit 1").one();
        return convertPurchaseRecordToDto(purchaseRecord);
    }


	@Override
	public Long saveUnPayBenefitPurchase(Long uid, @Valid SaveBenefitPurchaseUnPayDto saveDto) {
        TblBenefitPackage tblBenefitPackage = tblBenefitPackageService.getNftVipPackage(saveDto.getPackageId());
        if (tblBenefitPackage == null) {
            log.error("invalid packageId:{}", saveDto.getPackageId());
            throw new ChainException(ErrorMessage.ARGUMENT_NOT_VALID);
        } else if (!purchaseAble(tblBenefitPackage)) {
            throw new ChainException(ErrorMessage.BENEFIT_PACKAGE_CAN_NOT_BE_PURCHASED);
        }

        LocalDateTime now = LocalDateTime.now();
        TblBenefitPurchaseRecord purchaseRecord = TblBenefitPurchaseRecord.builder()
                .packageId(saveDto.getPackageId())
                .recordId(saveDto.getRecordId())
                .purchaseNum(saveDto.getPurchaseNum())
                .appUserId(uid)
                .payStatus(BenefitPurchasePayStatusEnum.NOT_PAYED.code())
                .unitPrice(saveDto.getUnitPrice())
                .totalPaid(tblBenefitPackage.getSalePrice().multiply(new BigDecimal(saveDto.getPurchaseNum())))
                .createTime(now)
                .updateTime(now)
                .isDel(0)
                .build();
        tblBenefitPurchaseRecordService.save(purchaseRecord);
        return purchaseRecord.getRecordId();
    }

	@Override
	public TblBenefitPurchaseRecordDto getBenefitPurchaseRecord(Long uid, GetBenefitPurchaseRecordRequest request) {
		return convertPurchaseRecordToDto(getBenefitPurchaseRecordById(uid, request.getRecordId()));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean handlePaySuccess(Long uid, HandlePaySuccessRequest request) {
        // 权益支付记录检查
        TblBenefitPurchaseRecord purchaseRecord = getBenefitPurchaseRecordById(uid, request.getRecordId());
        if (purchaseRecord == null) {
            log.error("invalid recordId:{}", request.getRecordId());
            throw new ChainException(ErrorMessage.ARGUMENT_NOT_VALID);
        } else if (!Objects.equals(purchaseRecord.getPayStatus(), BenefitPurchasePayStatusEnum.NOT_PAYED.code())) {
            log.error("recordId:{} status:{} can not be payed", request.getRecordId(), purchaseRecord.getPayStatus());
            throw new ChainException(ErrorMessage.BENEFIT_PURCHASE_RECORD_STATUS_ERROR);
        }

        // 更新权益支付记录状态
        LambdaUpdateWrapper<TblBenefitPurchaseRecord> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(TblBenefitPurchaseRecord::getRecordId, request.getRecordId())
                .eq(TblBenefitPurchaseRecord::getAppUserId, uid)
                .eq(TblBenefitPurchaseRecord::getPayStatus, BenefitPurchasePayStatusEnum.NOT_PAYED.code())
                .set(TblBenefitPurchaseRecord::getPayStatus, BenefitPurchasePayStatusEnum.PAID.code())
                .set(TblBenefitPurchaseRecord::getUpdateTime, LocalDateTime.now());
        boolean update = tblBenefitPurchaseRecordService.update(updateWrapper);
        if (!update) {
            log.error("recordId:{} status:{} can not be payed", request.getRecordId(), purchaseRecord.getPayStatus());
            throw new ChainException(ErrorMessage.BENEFIT_PURCHASE_RECORD_STATUS_ERROR);
        }

        TblBenefitPackage benefitPackage = tblBenefitPackageService.getById(purchaseRecord.getPackageId());
        UserBenefitSourceTypeEnum benefitSourceTypeEnum = UserBenefitSourceTypeEnum
                .valueOf(benefitPackage.getPackageCode());
        benefitService.sendBenefitPackage(benefitPackage, uid, purchaseRecord.getPurchaseNum(), benefitSourceTypeEnum,
                purchaseRecord.getRecordId(),purchaseRecord.getRecordId());
        return true;
    }

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean handlePayCancel(Long uid, HandlePayCancelRequest request) {
        // 权益支付记录检查
        TblBenefitPurchaseRecord purchaseRecord = getBenefitPurchaseRecordById(uid, request.getRecordId());
        if (purchaseRecord == null) {
            log.error("invalid recordId:{}", request.getRecordId());
            throw new ChainException(ErrorMessage.ARGUMENT_NOT_VALID);
        } else if (!Objects.equals(purchaseRecord.getPayStatus(), BenefitPurchasePayStatusEnum.NOT_PAYED.code())) {
            log.error("recordId:{} status:{} can not be canceled", request.getRecordId(),
                    purchaseRecord.getPayStatus());
            throw new ChainException(ErrorMessage.BENEFIT_PURCHASE_RECORD_STATUS_ERROR);
        }

        // 更新权益支付记录状态
        LambdaUpdateWrapper<TblBenefitPurchaseRecord> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(TblBenefitPurchaseRecord::getRecordId, request.getRecordId())
                .eq(TblBenefitPurchaseRecord::getAppUserId, uid)
                .eq(TblBenefitPurchaseRecord::getPayStatus, BenefitPurchasePayStatusEnum.NOT_PAYED.code())
                .set(TblBenefitPurchaseRecord::getPayStatus, BenefitPurchasePayStatusEnum.CANCEL.code())
                .set(TblBenefitPurchaseRecord::getUpdateTime, LocalDateTime.now());
        return tblBenefitPurchaseRecordService.update(updateWrapper);
    }

	private TblBenefitPurchaseRecord getBenefitPurchaseRecordById(Long uid, @NotNull Long recordId) {
        LambdaQueryChainWrapper<TblBenefitPurchaseRecord> query = tblBenefitPurchaseRecordService.lambdaQuery();
        return query.eq(TblBenefitPurchaseRecord::getAppUserId, uid)
                .eq(TblBenefitPurchaseRecord::getRecordId, recordId)
                .one();
    }

	private boolean purchaseAble(TblBenefitPackage tblBenefitPackage) {
        return tblBenefitPackage != null
                && Objects.equals(tblBenefitPackage.getIsDel(), 0)
                && Objects.equals(tblBenefitPackage.getOnSale(), 1);
    }

	private TblBenefitPurchaseRecordDto convertPurchaseRecordToDto(TblBenefitPurchaseRecord purchaseRecord) {
        if (purchaseRecord == null) {
            return null;
        }
        TblBenefitPurchaseRecordDto dto = new TblBenefitPurchaseRecordDto();
        BeanUtils.copyProperties(purchaseRecord, dto);
        return dto;
    }

	private TblBenefitPackageDto convertPackageToDto(TblBenefitPackage benefitPackage) {
        if (benefitPackage == null) {
            return null;
        }
        TblBenefitPackageDto dto = new TblBenefitPackageDto();
        BeanUtils.copyProperties(benefitPackage, dto);
        return dto;
    }

	private void fillUserVipBenefitData(Long uid, List<Long> allBenefitIds, List<BenefitItemVo> myBenefits) {
        List<TblUserBenefit> userBenefits = tblUserBenefitService.queryUserBenefits(uid, allBenefitIds);
        if (CollectionUtils.isEmpty(userBenefits)) {
            return;
        }

        Map<String, BenefitItemVo> myBenefitMap = new HashMap<>();
        for (TblUserBenefit userBenefit : userBenefits) {
            if (Objects.equals(userBenefit.getBenefitCode(), BenefitEnum.NFT_VIP_TAG.getCode())
                    || Objects.equals(userBenefit.getBenefitStatus(),USER_BENEFIT_BLOCKED)) {
                continue;
            }
            BenefitItemVo item = myBenefitMap.get(userBenefit.getBenefitCode());
            if (item != null) {
                item.setLimitCount(item.getLimitCount() + userBenefit.getBenefitNum());
                item.setLimitRemain(item.getLimitRemain() + userBenefit.getBenefitRemain());
            } else {
                item = new BenefitItemVo();
                item.setBenefitCode(userBenefit.getBenefitCode());
                item.setLimitCount(userBenefit.getBenefitNum());
                item.setLimitRemain(userBenefit.getBenefitRemain());
                myBenefitMap.put(userBenefit.getBenefitCode(), item);
            }
        }
        myBenefits.addAll(myBenefitMap.values());
    }

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void handlePaySuccess(Long businessOrderId) {
        // 分布式锁保护,防止重复处理
        String lockKey = String.format("nft_vip_order_handle_%s", businessOrderId);
        String lockValue = String.valueOf(System.currentTimeMillis());
        boolean lock = false;
        try {
            lock = redisLockUtil.tryLock(lockKey, lockValue, 1, TimeUnit.MINUTES);
            if (!lock) {
                log.error("handlePaySuccess lock failed, businessOrderId:{}", businessOrderId);
                throw new ChainException(ErrorMessage.UNKNOWN_ERROR);
            }
            BusinessOrderResponse businessOrder = iInnerService.businessOrderInfoWithId(businessOrderId);
            isNull(businessOrder, ErrorMessage.BUSINESS_ORDER_NOT_EXIST);

            // 检查订单是否已处理
            long count = tblBenefitPurchaseRecordService.countByBusinessOrderId(businessOrderId);
            if (count > 0) {
                log.error("handlePaySuccess businessOrderId:{} already processed", businessOrderId);
                return;
            }

            long packageId = Long.parseLong(businessOrder.getGoodsId());
            TblBenefitPackage benefitPackage = tblBenefitPackageService.getById(packageId);
            isNull(benefitPackage, ErrorMessage.BENEFIT_PACKAGE_NOT_EXIST);

            // step1: 保存权益领取记录
            LocalDateTime now = LocalDateTime.now();
            TblBenefitPurchaseRecord purchaseRecord = TblBenefitPurchaseRecord.builder()
                    .recordId(IdUtil.getSnowflakeNextId())
                    .packageId(benefitPackage.getPackageId())
                    .appUserId(businessOrder.getAppUserId())
                    .businessOrderId(businessOrderId)
                    .payStatus(BenefitPurchasePayStatusEnum.PAID.code())
                    .purchaseNum(businessOrder.getPurchaseNum())
                    .unitPrice(businessOrder.getRealPrice())
                    .totalPaid(businessOrder.getTotalPrice())
                    .payStatus(BenefitPurchasePayStatusEnum.PAID.code())
                    .createTime(now)
                    .updateTime(now)
                    .isDel(0)
                    .biz_type(BENEFIT_PURCHASE_BIZ_NFT_VIP_PURCHASE)
                    .build();
            isFalse(tblBenefitPurchaseRecordService.save(purchaseRecord),
                    ErrorMessage.BENEFIT_PURCHASE_RECORD_SAVE_FAILED);

            // step2: 发送权益
            UserBenefitSourceTypeEnum sourceTypeEnum = calculateVipSourceTypeEnum(benefitPackage.getPackageCode());
            benefitService.sendBenefitPackage(benefitPackage, businessOrder.getAppUserId(),
                    businessOrder.getPurchaseNum(),
                    sourceTypeEnum, purchaseRecord.getRecordId(), purchaseRecord.getRecordId());

        } finally {
            redisLockUtil.releaseLock(lockKey, lockValue);
        }

    }

	private UserBenefitSourceTypeEnum calculateVipSourceTypeEnum(String packageCode) {
		switch (packageCode) {
		case "NFT_VIP_MONTH":
			return UserBenefitSourceTypeEnum.NFT_VIP_MONTH;
		case "NFT_VIP_SEASON":
			return UserBenefitSourceTypeEnum.NFT_VIP_SEASON;
		case "NFT_VIP_YEAR":
			return UserBenefitSourceTypeEnum.NFT_VIP_YEAR;
		default:
			throw new ChainException(ErrorMessage.UNKNOWN_ERROR);
		}
	}
}
